package networkcontroller;

import java.io.DataInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import commands.controller.CC_NC_HostAlive;
import commands.controller.CC_NC_SearchResult;
import commands.nc.NCErrorInternal;
import commands.nc.NCGetInternal;
import commands.nc.NCSearchInternal;
import commands.nc.NCSegmentInternal;

import controller.Controller;
import dataTypes.Host;

import networkcontroller.packet.Packet;
import networkcontroller.xml.XMLAlive;
import networkcontroller.xml.XMLGet;
import networkcontroller.xml.XMLResult;
import networkcontroller.xml.XMLSearch;

/**
 * handles single tcp connection
 * 
 * @author maciek
 * 
 */
public class TCPConnection implements Runnable
{
	/**
	 * socket which is handling this connection
	 */
	private Socket						socket;
	/**
	 * reference to controller
	 */
	private final Controller			ctrl;
	/**
	 * reference to network controller
	 */
	private final NetworkController		netCtrl;
	/**
	 * packet to send first in this connection
	 */
	private final Packet				packet;
	/**
	 * destination host
	 */
	private final String				host;
	/**
	 * destination host port
	 */
	private final int					port;
	/**
	 * input queue of packets
	 */
	private final BlockingQueue<Packet>	inputQueue	= new LinkedBlockingQueue<Packet>();

	/**
	 * constructor used for incoming connections, starts tcp connection thread
	 * 
	 * @param socket
	 *            socket which handle connection
	 * @param ctrl
	 *            reference to controller
	 * @param netCtrl
	 *            reference to network controller
	 */
	TCPConnection(Socket socket, Controller ctrl, NetworkController netCtrl)
	{
		this.socket = socket;
		this.ctrl = ctrl;
		this.netCtrl = netCtrl;
		this.packet = null;
		this.host = null;
		this.port = 0;
		new Thread(this).start();
	}

	/**
	 * constructor used for outgoing connections, starts tcp connection thread
	 * 
	 * @param host
	 *            destination host to connect to
	 * @param port
	 *            destination host port
	 * @param ctrl
	 *            reference to controller
	 * @param netCtrl
	 *            reference to network controller
	 * @param packet
	 *            to send first
	 */
	public TCPConnection(String host, int port, Controller ctrl, NetworkController netCtrl, Packet packet)
	{
		super();
		this.socket = null;
		this.ctrl = ctrl;
		this.netCtrl = netCtrl;
		this.packet = packet;
		this.host = host;
		this.port = port;
		new Thread(this).start();
	}

	public void run()
	{
		try
		{
			System.out.println("NC: new connection");
			if (socket == null)
			{
				System.out.println("NC: connecting to:" + host + ":" + port);
				socket = new Socket(host, port);
			}
			InputStream is = socket.getInputStream();
			DataInputStream dis = new DataInputStream(is);
			OutputStream os = socket.getOutputStream();
			if (packet != null)
			{
				os.write(packet.getPacketData());
			}
			if (packet == null || packet.isNeedReply())
			{
				long size = dis.readLong();
				byte messageType = dis.readByte();
				byte[] data = new byte[(int) size - Packet.MESSEGE_TYPE_SIZE];
				dis.readFully(data);
				if (messageType != Packet.SEGMENT_CODE)
					System.out.println("NC: TCP data:" + new String(data));
				switch (messageType)
				{
				case Packet.SEARCH_CODE:
					try
					{
						XMLSearch xmlSearch = new XMLSearch(data);
						System.out.println("NC: XML search:" + xmlSearch.getHost() + ":" + xmlSearch.getPort());
						netCtrl.writeMsg(new NCSearchInternal(xmlSearch.getName(), xmlSearch.getSizeMin(), xmlSearch.getSizeMax(),
								xmlSearch.getSum(), xmlSearch.getId(), this));
						Packet packet = inputQueue.take();
						os.write(packet.getPacketData());
						socket.close();
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
					break;
				case Packet.GET_CODE:
					try
					{
						XMLGet xmlGet = new XMLGet(data);
						netCtrl.writeMsg(new NCGetInternal(xmlGet.getSum(), xmlGet.getSize(), xmlGet.getSegment(), this));
						Packet packet = inputQueue.take();
						os.write(packet.getPacketData());
						socket.close();
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
					break;
				case Packet.RESULT_CODE:
					System.out.println("NC: RESULT packet received");
					try
					{
						XMLResult xmlResult = new XMLResult(data);
						ctrl.writeMsg(new CC_NC_SearchResult(new Host(xmlResult.getHost()), xmlResult.getFileList(), xmlResult.getId()));
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
					break;
				case Packet.SEGMENT_CODE:
					try
					{
						netCtrl.writeMsg(new NCSegmentInternal(data, this));
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
					break;
				case Packet.ALIVE_CODE:
					try
					{
						XMLAlive xmlAlive = new XMLAlive(data);
						ctrl.writeMsg(new CC_NC_HostAlive(new Host(xmlAlive.getHost())));
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
					break;
				case Packet.REPEAT_CODE:
					// unimplementable
				}
				socket.close();
			}
		} catch (IOException e)
		{
			try
			{
				netCtrl.writeMsg(new NCErrorInternal(this));
			} catch (InterruptedException e2)
			{
				e2.printStackTrace();
			}
		}
	}

	/**
	 * puts commands to incoming command queue
	 * 
	 * @param cmd
	 *            incoming command
	 * @throws InterruptedException
	 */
	public void writeMsg(Packet packet) throws InterruptedException
	{
		synchronized (inputQueue)
		{
			if (inputQueue.remainingCapacity() == 0)
			{
				System.out.println("NC: TCPConnection writeMsg(): input queue is full - blocking call");
			}
			inputQueue.put(packet);
		}
	}
}
